/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.i18n.manager;

import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.i18n.I18nContext;
import io.iec.edp.caf.i18n.api.ResourceLocalizer;
import io.iec.edp.caf.i18n.utils.MyFileFilter;
import io.iec.edp.caf.i18n.utils.ResourceMetadataMapper;
import io.iec.edp.caf.msu.api.ServiceUnitAwareService;
import io.iec.edp.caf.msu.api.entity.ServiceUnitInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 国际化资源服务接口实现类
 *
 * @author Vincent Man
 * @date 2023-03-27
 */
@Slf4j
public class ResourceLocalizerImpl implements ResourceLocalizer {

    private static final BASE64Decoder Decoder = new BASE64Decoder();

    //资源文件与ResourceBundle的映射map     <language--su--resource, lastModifiedTime>
    private final static Map<String, ResourceBundle> ResourceSet = new ConcurrentHashMap<>();

    //su与su路径映射map      <su, suPath>
    private final static Map<String, String> SuPathSet = new ConcurrentHashMap<>();

    //.resmap映射文件集合     <su, <metadataId, properties>>
    private final static Map<String, HashMap<String, String>> MapperSet = new ConcurrentHashMap<>();

    //.properties文件最后修改时间集合(决定是否重新获取资源文件内容)     <language--su--resource, lastModifiedTime> + <"Mapper--" + su + "--" + name, lastModifiedTime>
    private final static Map<String, Long> FileLastModifiedTimeSet = new ConcurrentHashMap<>();

    /**
     * 根据资源编号{code}，su{su}，语言{language}和 资源文件名称/资源元数据id{resource} 获取对应语言资源值
     *
     * @param code     资源编号
     * @param resource 资源文件名或资源元数据id
     * @param su       服务单元编号
     * @param language 语言
     * @return 对应语言文本资源。code或resource为空返回null，资源文件中无此code返回""
     */
    @Override
    public String getString(String code, String resource, String su, String language) {
        String result = null;
        if (StringUtils.isNotEmpty(code) && StringUtils.isNotEmpty(resource)) {
            su = getSu(su);
            language = getLanguage(language);
            try {
                result = getStringByResourceID(code, resource, su, language);
            } catch (Exception e) {
                throw new RuntimeException("Error getting string resource by [" + code + ", " + resource + ", " + su + ", " + language + "], Please check whether the resource file or code exists !", e);
            }
        }

        return result;
    }

    /**
     * 根据资源编号{code}、su{su}、语言{language}和资源文件名称/资源元数据id{resource}获取对应语言的二进制资源
     *
     * @param code     资源编号
     * @param resource 资源文件名或资源元数据id
     * @param su       服务单元编号
     * @param language 语言
     * @return 对应语言二进制资源。code或resource为空返回null，资源文件中无此code返回""
     */
    @Override
    public byte[] getBytes(String code, String resource, String su, String language) {
        byte[] result = null;
        if (StringUtils.isNotEmpty(code) && StringUtils.isNotEmpty(resource)) {
            su = getSu(su);
            language = getLanguage(language);
            try {
                result = getImageByResourceID(code, resource, su, language);
            } catch (Exception e) {
                throw new RuntimeException("Error getting bytes resource by [" + code + ", " + resource + ", " + su + ", " + language + "], Please check whether the resource file or code exists !", e);
            }
        }

        return result;
    }

    /**
     * 根据资源编号{code}、su{su}、语言{language}和资源文件名称/资源元数据id{resource}获取对应语言的图片资源
     *
     * @param code     资源编号
     * @param resource 资源文件名或资源元数据id
     * @param su       服务单元编号
     * @param language 语言
     * @return 对应语言图片资源。code或resource为空返回null，资源文件中无此code返回""
     */
    @Override
    public Image getImage(String code, String resource, String su, String language) {
        Image result = null;
        try {
            byte[] results = getBytes(code, resource, su, language);
            if (results != null) {
                String base64String = new String(results, StandardCharsets.UTF_8);
                byte[] bytes = Decoder.decodeBuffer(base64String);
                ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
                result = ImageIO.read(stream);
            }
        } catch (Exception e) {
            throw new RuntimeException("Error getting image resource by [" + code + ", " + resource + ", " + su + ", " + language + "], Please check whether the resource file or code exists !", e);
        }

        return result;
    }

    /**
     * 根据资源元数据id{metadataId}、su{su}、语言{language}获取对应语言的资源集
     *
     * @param metadataId 资源元数据id
     * @param su         服务单元编号
     * @param language   语言
     * @return 对应语言资源集
     */
    @Override
    public ResourceBundle getResourceSet(String metadataId, String su, String language) {
        ResourceBundle result = null;
        if (StringUtils.isNotEmpty(metadataId)) {
            su = getSu(su);
            language = getLanguage(language);
            try {
                result = getResourceSetByMetadataID(metadataId, su, language);
            } catch (Exception e) {
                throw new RuntimeException("Error getting ResourceSet by [" + metadataId + ", " + su + ", " + language + "], Please check whether the resource file or code exists !", e);
            }
        }

        return result;
    }

    /**
     * 根据资源编号返回默认语言下的资源信息
     */
    private String getStringByResourceID(String code, String resource, String su, String language) {
        String fileName = getResourceFileName(resource, su);
        //先尝试获取自定义带语言的文件名
        ResourceBundle bundle = getCustomResourceBundle(fileName, su, language);
        if (bundle == null) {
            //如果未取出，则按原文件名获取
            bundle = getResourceBundle(fileName, su, language);
        }

        //获取code对应的value
        String value = "";
        if (bundle != null) {
            try {
                value = bundle.getString(code);
            } catch (MissingResourceException e) {
                //找到资源文件，但是资源文件中没有资源的报错异常
            }
        }

        return value;
    }

    /**
     * 根据资源编号返回默认语言下的资源信息
     */
    private byte[] getImageByResourceID(String resourceID, String contentID, String currentSU, String langCode) {
        String fileName = getResourceFileName(contentID, currentSU);
        //先尝试获取自定义带语言的文件名
        ResourceBundle bundle = getCustomResourceBundle(fileName, currentSU, langCode);
        if (bundle == null) {
            //如果未取出，则按原文件名获取
            bundle = getResourceBundle(fileName, currentSU, langCode);
        }

        //获取code对应的value
        byte[] value = null;
        if (bundle != null) {
            try {
                value = bundle.getString(resourceID).getBytes(StandardCharsets.UTF_8);
            } catch (MissingResourceException e) {
                //找到资源文件，但是资源文件中没有资源的报错异常
            }
        }

        return value;
    }

    /**
     * 根据资源元数据id、su、language获取对应资源集
     */
    private ResourceBundle getResourceSetByMetadataID(String metadataId, String su, String language) {
        String fileName = getFileName(metadataId, su);
        return getResourceBundle(fileName, su, language);
    }

    /**
     * 根据文件名、语种获取ResourceBundle
     */
    private ResourceBundle getResourceBundle(String fileName, String su, String language) {
        //定义.properties标识（language--su--fileName）
        String dimension = String.format("%s--%s--%s", language, su, fileName);

        String resource = getSuResourcesPath(su);
        String path = resource + File.separator + language + File.separator + String.format("%s.%s.properties", fileName, language);
        if (!new File(path).exists()) {
            path = resource + File.separator + String.format("%s.properties", fileName);
        }

        //.properties文件不存在，返回null
        File file = new File(path);
        if (!file.exists()) return null;


        //resourceBundle不存在，第一次获取
        if (!ResourceSet.containsKey(dimension)) {
            FileLastModifiedTimeSet.put(dimension, file.lastModified());
            ResourceSet.put(dimension, extractResourceBundle(file));
        }

        //resourceBundle存在，但是文件已经更新
        if (ResourceSet.containsKey(dimension) && !FileLastModifiedTimeSet.get(dimension).equals(file.lastModified())) {
            FileLastModifiedTimeSet.put(dimension, file.lastModified());
            ResourceSet.put(dimension, extractResourceBundle(file));
        }

        return ResourceSet.get(dimension);
    }

    /**
     * 根据文件路径从外部读取资源包
     */
    private ResourceBundle extractResourceBundle(File file) {
        ResourceBundle rb = null;
        BufferedInputStream inputStream = null;
        InputStreamReader reader = null;
        try {
            inputStream = new BufferedInputStream(new FileInputStream(file));
            reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
            rb = new PropertyResourceBundle(reader);
        } catch (IOException e) {
            log.error("Error on reading .properties [{}] !", file.getAbsolutePath(), e);
        } finally {
            try {
                reader.close();
            } catch (IOException ignored) {
            }
            try {
                inputStream.close();
            } catch (IOException ignored) {
            }
        }

        return rb;
    }

    //获取languge
    private String getLanguage(String language) {
        return StringUtils.isEmpty(language) ? I18nContext.current.getLanguage() : language;
    }

    //获取su
    private String getSu(String su) {
        return StringUtils.isEmpty(su) ? "caf" : su.toLowerCase();
    }

    /**
     * 获取su的resources目录路径
     */
    private String getSuResourcesPath(String su) {
        return getSuPath(su) + File.separator + "resources";
    }

    /**
     * 获取su的路径
     */
    private String getSuPath(String su) {
        if (!SuPathSet.containsKey(su)) {
            ServiceUnitAwareService suService = SpringBeanUtils.getBean(ServiceUnitAwareService.class);
            if (suService != null) {
                List<ServiceUnitInfo> serviceUnits = suService.getAllServiceUnits();
                if (serviceUnits != null && serviceUnits.size() > 0) {
                    if (SuPathSet.isEmpty()) {
                        //如果suPathSet为空，把所有su放入
                        serviceUnits.forEach(info -> SuPathSet.put(info.getName().toLowerCase(), info.getPath()));
                        //CAF的path
                        SuPathSet.put("caf", CafEnvironment.getServerRTPath() + File.separator + "platform" + File.separator + "common");
                    } else {
                        //不为空，单独获取当前su
                        ServiceUnitInfo serviceInfo = serviceUnits.stream().filter(info -> su.equalsIgnoreCase(info.getName())).findFirst().orElse(null);
                        if (serviceInfo != null) SuPathSet.put(su, serviceInfo.getPath());
                    }
                }
            }
        }
        if (!SuPathSet.containsKey(su) || SuPathSet.get(su) == null) {
            log.error("Can't find path for su [" + su + "] !");
        }

        return SuPathSet.getOrDefault(su, "");
    }

    //获取.properties文件名
    private String getResourceFileName(String resource, String su) {
        if (StringUtils.endsWithIgnoreCase(resource, ".properties")) {
            //resource为资源文件名
            return resource.trim().substring(0, resource.length() - 11);
        } else {
            //resource为资源元数据id
            return getFileName(resource, su);
        }
    }

    /**
     * 获取自定义资源包
     */
    private ResourceBundle getCustomResourceBundle(String fileName, String su, String language) {
        //尝试获取自定义带语言的文件名
        String cusFileName = getCustomResourceName(fileName);
        return getResourceBundle(cusFileName, su, language);
    }

    /**
     * 根据资源元数据id、su获取元数据对应的资源文件名
     */
    private String getFileName(String metadataId, String su) {
        if (!MapperSet.containsKey(su)) {
            MapperSet.put(su, getMapperSet(su));
        }

        return MapperSet.get(su).get(metadataId);
    }

    /**
     * 读取su中.resmap文件内容
     */
    private HashMap<String, String> getMapperSet(String su) {
        HashMap<String, String> map = new HashMap<>();
        //资源文件路径下路径信息
        String resourcesPath = getSuResourcesPath(su);

        //读取资源文件路径下所有后缀为.resmap的文件信息
        File resources = new File(resourcesPath);
        String[] resmapFileNames = resources.list(new MyFileFilter("resmap"));

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        if (resmapFileNames != null) {
            for (String name : resmapFileNames) {
                File file = new File(resourcesPath + File.separator + name);

                //读取.resmap文件内容
                ResourceMetadataMapper mapper = null;
                try {
                    mapper = objectMapper.readValue(file, ResourceMetadataMapper.class);
                } catch (IOException e) {
                    //吞掉异常，记录日志
                    log.error("Error on reading .resmap [" + file.getAbsolutePath() + "] !", e);
                }
                if (mapper != null && mapper.getMetadataIds() != null) {
                    for (String id : mapper.getMetadataIds()) {
                        map.put(id, mapper.getProjectName());
                    }
                }
            }
        }

        return map;
    }

    /**
     * 根据文件名称获取用户扩展的资源文件
     */
    private String getCustomResourceName(String fileName) {
        return String.format("%s.custom", fileName);
    }

}